#import "FlutterWebsocketPlugin.h"
#import <SocketRocket/SocketRocket.h>


@interface WebSocketClient: NSObject<SRWebSocketDelegate, FlutterStreamHandler>
@property (nonatomic, strong) SRWebSocket *webSocket;
-(instancetype)initWithUrlString:(NSString *)urlString
                      identifier:(NSString *)identifier
                 binaryMessenger:(nonnull NSObject<FlutterBinaryMessenger> *)binaryMessenger;

-(void)open;
-(BOOL)sendData:(NSData* _Nonnull)data error:(NSError **)error;
-(BOOL)sendString:(NSString *)string error:(NSError **)error;
-(void)close;
@end

@interface FlutterWebsocketPlugin()
@property(readonly, nonatomic) NSObject<FlutterBinaryMessenger> *binaryMessenger;
@property(readonly, nonatomic) NSMutableDictionary<NSString *, WebSocketClient*> *clients;
@end

@implementation FlutterWebsocketPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
    FlutterMethodChannel* channel = [FlutterMethodChannel
                                     methodChannelWithName:@"ext.plugin/flutter_websocket"
                                     binaryMessenger:[registrar messenger]];
    FlutterWebsocketPlugin* instance = [[FlutterWebsocketPlugin alloc] initWithRegistrar:registrar];
    [registrar addMethodCallDelegate:instance channel:channel];
}

-(instancetype) initWithRegistrar:(NSObject<FlutterPluginRegistrar>*) registrar {
    self = [super init];
    _binaryMessenger = registrar.messenger;
    _clients = [NSMutableDictionary dictionaryWithCapacity:1];
    return self;
}

- (void)detachFromEngineForRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
    [_clients removeAllObjects];
}

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
    if ([@"create" isEqualToString:call.method]) {
        NSString *identifier = call.arguments[@"id"];
        NSString *urlString = call.arguments[@"url"];
        WebSocketClient *client = [[WebSocketClient alloc] initWithUrlString:urlString identifier:identifier binaryMessenger:_binaryMessenger];
        [_clients setObject:client forKey:identifier];
        result(identifier);
    } else if ([@"connect" isEqualToString:call.method]) {
        NSString *identifier = call.arguments;
        WebSocketClient *client = _clients[identifier];
        [client open];
        result(nil);
    } else if ([@"send" isEqualToString:call.method]) {
        NSString *identifier = call.arguments[@"id"];
        NSData *data = [call.arguments[@"data"] data];
        WebSocketClient *client = _clients[identifier];
        NSError *error;
        [client sendData:data error: &error];
        if (error) {
            result(@{
                @"error_code": @(error.code),
                @"error_msg": error.description,
            });
        } else {
            result(nil);
        }
    } else if ([@"sendText" isEqualToString:call.method]) {
        NSString *identifier = call.arguments[@"id"];
        NSString *text = call.arguments[@"text"];
        WebSocketClient *client = _clients[identifier];
        NSError *error;
        [client sendString:text error: &error];
        if (error) {
            result(@{
                @"error_code": @(error.code),
                @"error_msg": error.description,
            });
        } else {
            result(nil);
        }
    } else if ([@"close" isEqualToString:call.method]) {
        NSString *identifier = call.arguments;
        WebSocketClient *client = _clients[identifier];
        [_clients removeObjectForKey:identifier];
        [client close];
        result(nil);
    } else {
        result(FlutterMethodNotImplemented);
    }
}

@end


@implementation WebSocketClient {
    FlutterEventChannel *_eventChannel;
    FlutterEventSink _eventSink;
}

- (instancetype)initWithUrlString:(NSString *)urlString
                       identifier:(NSString *)identifier
                  binaryMessenger:(nonnull NSObject<FlutterBinaryMessenger> *)binaryMessenger {
    if (self = [super init]) {
        NSString *channelName = [NSString stringWithFormat:@"ext.plugin/websocketEvents%@", identifier];
        _eventChannel = [FlutterEventChannel eventChannelWithName:channelName binaryMessenger:binaryMessenger];
        [_eventChannel setStreamHandler:self];
        self.webSocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:urlString]];
        self.webSocket.delegate = self;
    }
    return self;
}

- (void)open {
    [self.webSocket open];
    if (_eventSink) {
        _eventSink(@{
            @"event": @"onState",
            @"data": @(self.webSocket.readyState)
        });
    }
}

- (BOOL)sendData:(NSData * _Nonnull)data error:(NSError **)error {
    return [_webSocket sendData:data error:error];
}

- (BOOL)sendString:(NSString *)string error:(NSError *__autoreleasing *)error {
    return [_webSocket sendString:string error:error];
}

- (void)close {
    [self.webSocket close];
    _eventSink(@{
        @"event": @"onState",
        @"data": @(self.webSocket.readyState)
    });
}

- (void)webSocketDidOpen:(SRWebSocket *)webSocket {
    NSLog(@"WebSocket did open");
    _eventSink(@{
        @"event": @"onState",
        @"data": @(webSocket.readyState)
    });
}

- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
    NSLog(@"WebSocket did fail with error: %@", error);
}

- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
    NSLog(@"WebSocket did receive message: %@", message);
    NSString *text = message;
    _eventSink(@{
        @"event": @"onIncoming",
        @"data": [text dataUsingEncoding:NSUTF8StringEncoding]
    });
}

- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {
    NSLog(@"WebSocket did close with code: %ld, reason: %@", (long)code, reason);
    if (_eventSink) {
        _eventSink(@{
            @"event": @"onState",
            @"data": @(self.webSocket.readyState)
        });
    }
}

- (FlutterError * _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events {
    _eventSink = events;
    return nil;
}

- (FlutterError * _Nullable)onCancelWithArguments:(id _Nullable)arguments {
    _eventSink = nil;
    return nil;
}



@end
